
#include "fx_reflection.h"
#include "render.h"
#include "graphics_functions.h"

using namespace WtfEngine;

PlanarReflection::PlanarReflection(const WtfEngine::PlanarReflection::tCreationOptions &opts,
	const WtfGraphics::GraphicsData::tCacheRef &rGfx, const WtfEngine::PhysicalModel::tCacheRef &rModel)
: Static(opts, rGfx, rModel), mPlane(opts.vPlanarNormal.normalize()) {

};

void PlanarReflection::Render() {
	// Hack to handle reentrancy..
	static bool isRendering = false;

	if(!isRendering) {
		isRendering = true;

		//
		// Find intersection point of camera's view ray with plane, based upon
		// normal representation of plane.
		//

		ICamera::tRef rCamera = Renderer::GetInstance().GetCamera();
		tPosition vCamPos = rCamera->getPosition();
		tDirection vCamDir = rCamera->getDirection();

		
		tPlane plane(mPlane, getPosition().dot(mPlane)); // mPlane is normalized
		tPosition vIntersection = getPosition();

		// Create the transform, and calculate the reflected eye direction.
		Transformation3D t = CreateTransform(plane, vIntersection);
		tDirection vDirection = t * ((vCamPos + vCamDir) - vIntersection);

		// Setup the OpenGL stencil buffer
		//glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		glDepthMask(GL_FALSE);

		glClearStencil(0);
		glClear(GL_STENCIL_BUFFER_BIT);

		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

		// Draw surface into stencil buffer
		Static::Render();

		// Now draw reflections, with stencil buffer on
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glDepthMask(GL_TRUE);

		glStencilFunc(GL_EQUAL, 1, 0xffffffff);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

		
		// Push transformation and render reflected objects
		// TODO: clipping based upon new eye vector
		WtfGraphics::SaveView();
		WtfGraphics::Transform(t);

		Renderer::GetInstance().RenderAll();

		WtfGraphics::RestoreView();
		glDisable(GL_STENCIL_TEST);
		
		// Render the reflective surface, with blending
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		Static::Render();
		glDisable(GL_BLEND);

		isRendering = false;
	};
};

/// Takes the world coordinate, translates to intersection space, reflects about the plane, then translates
/// back to world space.
Transformation3D PlanarReflection::CreateTransform(const tPlane& plane, const tPosition& vIntersection) {
	return Transformation3D(-vIntersection, Orientation3D::identity()).then(Transformation3D(
		vIntersection, plane.vNormal.householder()));
};


ObliqueFrustumReflection::ObliqueFrustumReflection(const tVector &vPosition, const tPlane &plane, const iVector2D &vResolution)
: FBOTexture()
//mHouseholderMatrix(plane.vNormal.householder())
{
	FBOTexture::Create(vResolution[0], vResolution[1]);
};

void ObliqueFrustumReflection::setCamera(const WtfEngine::ICamera::tRef &rCamera) {
	mrCamera = rCamera;
};

void ObliqueFrustumReflection::EnterPerspective(const WtfEngine::IScreen::tRef &pScreen) const {
	//
	// Camera setup
	// Calculate the intersection point of the real camera's sight ray with the plane, then
	// use this as the origin for a householder matrix transformation, reflecting in the plane
	// to give the reflected sight ray (from the intersection point)
	//

	tPosition vCamPos = mrCamera->getPosition();
	tDirection vCamDir = mrCamera->getDirection();

	tPosition vIntersection = vCamPos +
		(vCamDir * ((mPlane.vNormal.dot(vCamPos) - mPlane.fDistance) / mPlane.vNormal.dot(vCamDir)));

	tDirection vDirection = mHouseholderMatrix
		* ((mrCamera->getPosition() + mrCamera->getDirection()) - vIntersection);

	// XXX - making assumptions...
	tDirection vUpDirection = tDirection(0, 1, 0);


	// OpenGL
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	gluPerspective(45.0f, // 45 degree field of view
		(tScalar)(pScreen->getSize().x()) / (tScalar)(pScreen->getSize().y()),	// Aspect ratio
		0.1f, 2000.0f);	// Near and far clipping planes

	vDirection += vIntersection;

	gluLookAt(vIntersection[0],vIntersection[1],vIntersection[2],
			  vDirection[0],vDirection[1],vDirection[2],
			  vUpDirection[0], vUpDirection[1], vUpDirection[2]);

	glMatrixMode(GL_MODELVIEW);
};

void ObliqueFrustumReflection::LeavePerspective() const
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
};

void ObliqueFrustumReflection::Update() {
	// Render the reflection, based on main camera position
	this->setCamera(Renderer::GetInstance().GetCamera());
	Renderer::GetInstance().Render(this, this);
};
